#pragma once
#include <memory>
#include "is_type.h"

namespace nef {

class Catalog;

struct non_shared
{
	template<typename T, typename IxType>
	struct factory
	{
		static_assert(is_type<IxType, T>::value, "T must inherit from IxType");

		std::shared_ptr<IxType> operator()(Catalog &cat) const
		{
			auto sp = std::make_shared<T>();

			cat.ComposePart(*sp);

			return sp;
		}
	};

	template<typename T, typename IxType>
	static factory<T, IxType> Scope()
	{
		return factory<T, IxType>();
	}

	template<typename T>
	static factory<T, T> Scope()
	{
		return factory<T, T>();
	}
};

struct shared
{
	template<typename T>
	struct shared_provider
	{
		typedef std::shared_ptr<T> TPtr;

		shared_provider() {}
		shared_provider(const TPtr &val)
			: _memVal(val)
		{
		}

		TPtr get(Catalog &cat) const
		{
			static TPtr VAL;

			if (_memVal)
				return _memVal;

			if (!VAL)
			{
				VAL.reset(new T);
				cat.ComposePart(*VAL);
			}

			return VAL;
		}

	private:
		TPtr _memVal;
	};

	template<typename T, typename IxType>
	struct factory
	{
		static_assert(is_type<IxType, T>::value, "T must inherit from IxType");

		factory() {}
		factory(const std::shared_ptr<T> &val)
			: _provider(val)
		{
		}

		std::shared_ptr<IxType> operator()(Catalog &cat) const
		{
			return _provider.get(cat);
		}

		shared_provider<T> _provider;
	};

	template<typename T, typename IxType>
	static factory<T, IxType> Scope()
	{
		return factory<T, IxType>();
	}
	template<typename T, typename IxType>
	static factory<T, IxType> Scope(const std::shared_ptr<T> &val)
	{
		return factory<T, IxType>(val);
	}


	template<typename T>
	static factory<T, T> Scope()
	{
		return factory<T, T>();
	}

	template<typename T>
	static factory<T, T> Scope(const std::shared_ptr<T> &val)
	{
		return factory<T, T>(val);
	}

	template<typename T>
	static factory<T, T> Scope(T *val)
	{
		return Scope(std::shared_ptr<T>(val));
	}
};

struct shared_volatile
{
	template<typename T, typename IxType>
	struct factory
	{
		static_assert(is_type<IxType, T>::value, "T must inherit from IxType");

		std::shared_ptr<IxType> operator()(Catalog &cat) const
		{
			auto spVal = _val.lock();

			if (spVal)
				return spVal;

			spVal.reset(new T);
			cat.ComposePart(*spVal);

			_val = spVal;

			return spVal;
		}

		mutable std::weak_ptr<T> _val;
	};

	template<typename T, typename IxType>
	static factory<T, IxType> Scope()
	{
		return factory<T, IxType>();
	}

	template<typename T>
	static factory<T, T> Scope()
	{
		return factory<T, T>();
	}
};

struct dynamic 
{
	template<typename fn>
	struct factory 
	{
		factory(const fn f)
			: _fn(f)
		{ }

		auto operator()(Catalog &cat) const -> decltype((*(fn*)nullptr)())
		{
			return _fn(cat);
		}

		fn _fn;
	};

	template<typename Fn>
	static factory<Fn> Scope(const Fn &f)
	{
		return factory<Fn>(f);
	}
};

}